package com.myorg.search;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.primefaces.component.tabview.Tab;
import org.primefaces.event.TabChangeEvent;
import org.primefaces.model.DefaultTreeNode;
import org.primefaces.model.TreeNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.myorg.domain.Email;
import com.myorg.domain.SearchRequest;
import com.myorg.domain.SearchResult;
import com.myorg.service.ScanService;
import com.myorg.service.SearchService;
import com.myorg.service.StartupService;
import com.myorg.util.ConstantsToBeExternalized;
import com.myorg.util.HdfsHelper;
import com.myorg.util.HdfsHelper.IHDFSFileTreeNodeProcessor;
import com.myorg.util.Utility;

@ManagedBean
@ViewScoped
public class SearchBean extends BaseBean implements Serializable {
	private static final long serialVersionUID = -197825662428660665L;
	final static private Logger logger = LoggerFactory.getLogger(SearchBean.class);

	private String key;

	private TreeNode root;

	private TreeNode[] selectedNodes;

	private String executionTime;

	private List<Email> loadedEmails;

	private Email selectedEmail;

	private int currentPage = -1;

	private boolean allFetched = false;

	@PostConstruct
	public void init() {
		StartupService.touch();
		loadTree();
		initSearchResult();
	}

	private void initSearchResult() {
		loadedEmails = new ArrayList<Email>();
		currentPage = -1;
		allFetched = false;
		selectedEmail = null;
	}

	public static class PageNumber {
		private int num;
		private boolean current;

		public int getNum() {
			return num;
		}

		public void setNum(int num) {
			this.num = num;
		}

		public boolean isCurrent() {
			return current;
		}

		public void setCurrent(boolean current) {
			this.current = current;
		}
	}

	public int getMaxPage() {
		int number = 0;
		while (true) {
			if (loadedEmails.size() > number * ConstantsToBeExternalized.DEFAULT_NUM_OF_EMAILS_PER_PAGE) {
				number++;
			} else {
				break;
			}
		}
		return number - 1;
	}

	public Email getSelectedEmail() {
		return selectedEmail;
	}

	public void setSelectedEmail(Email selectedEmail) {
		this.selectedEmail = selectedEmail;
	}

	public List<PageNumber> getPageNumbers() {
		List<PageNumber> rtn = new ArrayList<PageNumber>();
		int number = 1;
		while (true) {
			if (loadedEmails.size() > (number - 1) * ConstantsToBeExternalized.DEFAULT_NUM_OF_EMAILS_PER_PAGE) {
				PageNumber pn = new PageNumber();
				pn.setNum(number);
				pn.setCurrent(number - 1 == currentPage);
				rtn.add(pn);
			} else {
				break;
			}
			number++;
		}
		return rtn;
	}

	public void onClickEmail(String id) {
		StartupService.touch();
		for (Email e : loadedEmails) {
			if (e.getId().equals(id)) {
				selectedEmail = e;
				break;
			}
		}
	}

	public void onClickPage(int p) {
		StartupService.touch();
		currentPage = p - 1;
	}

	public void onClickNextPage() {
		StartupService.touch();
		if (!loadedEmails.isEmpty() && currentPage < getMaxPage()) {
			currentPage++;
		} else {
			SearchResult sr = doSearch(ConstantsToBeExternalized.DEFAULT_NUM_OF_EMAILS_PER_PAGE);
			logger.info("Search Result Size: " + sr.getList().size());
			if (sr.getList().isEmpty()) {
				allFetched = true;
			} else {
				currentPage++;
			}
			loadedEmails.addAll(sr.getList());
		}
	}

	public void onClickPrevPage() {
		StartupService.touch();
		currentPage--;
	}

	public String getKey() {
		return StringUtils.trim(key);
	}

	public void setKey(String key) {
		this.key = key;
	}

	private void loadTree() {
		if (root == null) {
			FileSystem fs = null;
			try {
				HdfsHelper heler = new HdfsHelper();
				Configuration conf = heler.getConfig();
				fs = FileSystem.get(conf);
				root = new DefaultTreeNode(new EmailFile(fs.getFileStatus(new Path("/"))), null);
				root.setExpanded(true);
				heler.walkthrough(fs, root, "/", new IHDFSFileTreeNodeProcessor() {
					@Override
					public TreeNode process(TreeNode parent, FileStatus fs, String startRootPath, String parentDir) {
						String path = "/".equals(parentDir) ? "/" + fs.getPath().getName() : parentDir + "/"
								+ fs.getPath().getName();
						if (path.startsWith(startRootPath) || startRootPath.startsWith(path)) {
							TreeNode tn = new DefaultTreeNode(new EmailFile(fs), parent);
							tn.setExpanded(true);
							return tn;
						}
						return null;
					}
				}, ConstantsToBeExternalized.DEFAULT_DIR_FILES_UPLOAD);
			} catch (IOException e) {
				e.printStackTrace();
				throw new RuntimeException(e);
			} finally {
				if (fs != null) {
					try {
						fs.close();
					} catch (IOException e) {
					}
				}
			}
		}
	}

	public TreeNode[] getSelectedNodes() {
		return selectedNodes;
	}

	public void setSelectedNodes(TreeNode[] selectedNodes) {
		this.selectedNodes = selectedNodes;
	}

	public void refreshTree() {
		StartupService.touch();
		root = null;
		loadTree();
		selectedNodes = null;
		executionTime = null;
	}

	public void onTabChange(TabChangeEvent event) {
		StartupService.touch();
		if (event != null) {
			Tab tab = event.getTab();
			if (tab != null) {
				logger.info("Tab " + tab.getTitle() + " is selected.");
				if ("Search Email".equals(tab.getTitle())) {
					refreshTree();
				}
			}
		}
	}

	public void deleteFiles() {
		StartupService.touch();
		String[] paths = getSelectedPaths();
		if (paths.length > 0) {
			HdfsHelper helper = new HdfsHelper();
			for (String path : paths) {
				helper.removeFile(path);
			}
		}
		refreshTree();
	}

	public void scanFiles() {
		StartupService.touch();
		final String[] paths = getSelectedPaths();
		if (paths.length > 0) {
			String msg = Utility.measureExecutionTime(new Runnable() {
				@Override
				public void run() {
					SearchRequest searchReq = new SearchRequest().setFilePaths(paths);
					HttpServletRequest origRequest = (HttpServletRequest) FacesContext.getCurrentInstance()
							.getExternalContext().getRequest();
					new ScanService().dispatchScan(searchReq, origRequest.getContextPath(),
							origRequest.getServerPort(), null);
				}
			});
			addMessage("Success", "Scan took " + msg);
		}
	}

	private String[] getSelectedPaths() {
		List<String> rtn = new ArrayList<String>();
		if (selectedNodes != null) {
			for (TreeNode tn : selectedNodes) {
				EmailFile ef = (EmailFile) tn.getData();
				if (!ef.isDir()) {
					rtn.add(ef.getPath());
				}
			}
		}
		return rtn.toArray(new String[rtn.size()]);
	}

	public void search() {
		StartupService.touch();
		initSearchResult();
		onClickNextPage();
	}

	private SearchResult doSearch(final int pageSize) {
		final SearchResult result = new SearchResult();
		final String[] paths = getSelectedPaths();
		if (paths.length > 0 && StringUtils.isNotEmpty(getKey())) {
			executionTime = Utility.measureExecutionTime(new Runnable() {
				@Override
				public void run() {
					SearchRequest searchReq = new SearchRequest().setKey(getKey()).setFilePaths(paths).setMax(pageSize)
							.setIgnoreIds(createIgnoreList());
					HttpServletRequest origRequest = (HttpServletRequest) FacesContext.getCurrentInstance()
							.getExternalContext().getRequest();
					// final String syncPath = SearchDispatcher.createZnode();
					List<Email> list = new SearchService().dispatchSearch(searchReq, origRequest.getContextPath(),
							origRequest.getServerPort(), null).getList();
					result.setList(list);
					// SearchDispatcher.deleteZnode(syncPath);
				}
			});
		}
		return result;
	}

	private List<String> createIgnoreList() {
		List<String> rtn = new ArrayList<String>();
		for (Email e : loadedEmails) {
			rtn.add(e.getId());
		}
		return rtn;
	}

	public TreeNode getRoot() {
		return root;
	}

	public String getExecutionTime() {
		return executionTime == null || "".equals(executionTime.trim()) ? "" : "Response Time: " + executionTime.trim();
	}

	public boolean isShowExecutionTime() {
		return executionTime != null && !"".equals(executionTime.trim());
	}

	public static class EmailFile implements Serializable, Comparable<EmailFile> {
		private static final long serialVersionUID = 1L;

		private String name;
		private String size;
		private String type;
		private String path;
		private boolean dir;

		public EmailFile(FileStatus fs) {
			name = fs.getPath().getName();
			if (fs.isDirectory()) {
				type = "Dir";
				dir = true;
			} else {
				String extension = getFileExtension(name);
				type = (extension == null ? "Unknown" : extension) + " File";
				dir = false;
			}
			size = dir ? "" : String.valueOf(fs.getLen()) + "B";
			path = fs.getPath().toString();
		}

		private String getFileExtension(String fileName) {
			int index = fileName.lastIndexOf(".");
			if (index != -1) {
				return fileName.substring(index + 1).toUpperCase().trim();
			}
			return null;
		}

		public String getPath() {
			return path;
		}

		public String getName() {
			return name;
		}

		public String getSize() {
			return size;
		}

		public String getType() {
			return type;
		}

		public boolean isDir() {
			return dir;
		}

		@Override
		public int compareTo(EmailFile o) {
			return getName().compareTo(o.getName());
		}

		public String getDesc() {
			return getName() + " (" + getSize() + ", " + getType() + ")";
		}
	}

	public List<Email> getLoadedEmails() {
		return loadedEmails;
	}

	public List<Email> getDisplayedEmails() {
		List<Email> rtn = currentPage >= 0 ? loadedEmails.subList(
				currentPage * ConstantsToBeExternalized.DEFAULT_NUM_OF_EMAILS_PER_PAGE,
				Math.min((currentPage + 1) * ConstantsToBeExternalized.DEFAULT_NUM_OF_EMAILS_PER_PAGE,
						loadedEmails.size())) : Collections.<Email> emptyList();
		return rtn;
	}

	public int getCurrentPage() {
		return currentPage;
	}

	public boolean isAllFetched() {
		return allFetched;
	}

	public boolean isShowPrev() {
		return currentPage > 0;
	}

	public boolean isShowNext() {
		return !allFetched || (currentPage < getMaxPage());
	}

	public boolean isShowSearchResult() {
		return !loadedEmails.isEmpty();
	}
}
